/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * TrainingBook
 * Copyright (C) Arnaud Duval 2010 <arnaud.duval@gmail.com>
 * 
 * Projects is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Projects is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <ActivityHeader.hpp>
#include <cassert>
#include <fstream>
#include <filesystem-utils.hpp>

///@brief Default constructor
ActivityHeader::ActivityHeader()
{
    id = "DefaultID";
    fcMoy = 0;
    fcMax = 0;
    distance = 0.;
    calories = 0;
    for(unsigned int i = 0 ; i < 7 ; i++)
        intensity[i] = 0.;
    cadence = 0;
    maxSpeed = 0.;
    power = 0.;
}

///@brief Copy constructor
///@param a ActivityHeader object to copy
ActivityHeader::ActivityHeader(const ActivityHeader& a)
{
    id = a.id;
    date = a.date;
    location = a.location;
    type = a.type;
    distance = a.distance;
    duration = a.duration;
    fcMoy = a.fcMoy;
    fcMax = a.fcMax;
    comments = a.comments;
    calories = a.calories;
    quality = a.quality;
    for(unsigned int i = 0 ; i < 7 ; i++)
        intensity[i] = a.intensity[i];
    maxSpeed = a.maxSpeed;
    cadence = a.cadence;
    power = a.power;
    //laps = a.laps;
    intervals = a.intervals;
}

///@brief Destructor
ActivityHeader::~ActivityHeader()
{

}
 
///@brief Overload of = operator
///@param a ActiovityHeader object to copy
ActivityHeader &ActivityHeader::operator=(const ActivityHeader& a)
{
    id = a.id;
    date = a.date;
    location = a.location;
    type = a.type;
    distance = a.distance;
    duration = a.duration;
    fcMoy = a.fcMoy;
    fcMax = a.fcMax;
    comments = a.comments;
    calories = a.calories;
    quality = a.quality;
    for(unsigned int i = 0 ; i < 7 ; i++)
        intensity[i] = a.intensity[i];
    maxSpeed = a.maxSpeed;
    cadence = a.cadence;
    power = a.power; 
    //laps = a.laps;
    intervals = a.intervals;
    return (*this);
}

/*///@brief Add a new lap header
///@param l LapHeader to add
void ActivityHeader::AddLap(LapHeader l)
{
    laps.push_back(l);
    ///@warning Recomputing of Activity Header values has been deactivated
    //Recompute();
}*/

///@brief Recompute header values from values in the laps headers
void ActivityHeader::Recompute()
{
    distance = 0.;
    duration.SetWithFormat("00:00:00", "hh,nn,ss");
    fcMoy = 0;
    fcMax = 0;
    calories = 0;
    for(unsigned int i = 0 ; i < 7 ; i++)
        intensity[i] = 0;
    maxSpeed = 0;
    cadence = 0;
    power = 0;
    /*for(std::list<LapHeader>::iterator iterLap = laps.begin() ; iterLap != laps.end() ; ++iterLap)
    {
        distance += (*iterLap).GetDistance();
        duration += (*iterLap).GetDuration();
        fcMoy += (*iterLap).GetFCMoy()*(*iterLap).GetDuration().ValueInSeconds();
        if((*iterLap).GetFCMax() > fcMax)
            fcMax = (*iterLap).GetFCMax();
        calories += (*iterLap).GetCalories();
        if((*iterLap).GetMaximumSpeed() > maxSpeed)
            maxSpeed = (*iterLap).GetMaximumSpeed();
        //power += (*iterLap).GetPower()*(*iterLap).GetDuration().ValueInSeconds();
        cadence += (*iterLap).GetCadence()*(*iterLap).GetDuration().ValueInSeconds();
    }*/

    /*if(!laps.empty())
    {
        date = (*(laps.begin())).GetDateTime();
        fcMoy /= duration.ValueInSeconds();
        cadence /= duration.ValueInSeconds();
        //power /= duration.ValueInSeconds();
    }*/
    
    ///@warning recomputing value of average power has been deactivated
    
    ///@todo Write the routine to compute header value from laps
}

///SETTERS

///@brief Set the distance value;
///@param ddist Given distance value to set
void ActivityHeader::SetDistance(double ddist)
{
    distance = ddist;
}

///@brief Set the duration value
///@brief dduration Duration value to be set
void ActivityHeader::SetDuration(Duration dduration)
{
    duration = dduration;
}

///@brief Set the date value
///@param ddate Date value to be set
void ActivityHeader::SetDateTime(DateTime ddate)
{
    date = ddate;
}

///@brief Set the activity type
///@param dtype TYpe of activity to be set
void ActivityHeader::SetActivityType(std::string dtype)
{
    type = dtype;
}

///@brief Set FC Moy value
///@param dFCMoy FC Moy value to be set
void ActivityHeader::SetFCMoy(unsigned int dFCMoy)
{
    fcMoy = dFCMoy;
}

///@brief Set FC Max value
///@param dFCMax FC Max value to be set
void ActivityHeader::SetFCMax(unsigned int dFCMax)
{
    fcMax = dFCMax;
}

///@brief Set the location
///@param dlocation Location value to be set
void ActivityHeader::SetLocation(std::string dlocation)
{
    location = dlocation;
}

///@brief Set the comments
///@param dcomments Comments value to be set
void ActivityHeader::SetComments(std::string dcomments)
{
    comments = dcomments;
}

///@brief Set the consumed calories
///@param dcalories Calories value to be set
void ActivityHeader::SetCalories(unsigned int dcalories)
{
    calories = dcalories;
}

///@brief Set the trained quality
///@param dquality Quality value to be set
void ActivityHeader::SetQuality(std::string dquality)
{
    quality = dquality;
}

///@brief Set the time spent in a given intensity zone
///@param zone Index of intensity zone
///@param time Time spent in intensity zone
void ActivityHeader::SetTimeInZone(unsigned int zone, double time)
{
    assert(zone >= 1);
    assert(zone <= 7);
    intensity[zone -1] = time;
}

///@brief Set the maximum speed
///@param dspeed Maximum speed value to be set
void ActivityHeader::SetMaximumSpeed(double dspeed)
{
    maxSpeed = dspeed;
}

///@brief Set the average power
///@param dpower Average power value to set
void ActivityHeader::SetAveragePower(double dpower)
{
    power = dpower;
}
        
        
///GETTERS
        
///@brief Return the ID
std::string ActivityHeader::GetId() const
{
    return id;
}
        
///@brief Return the distance
double ActivityHeader::GetDistance()
{
    return distance;
}

///@brief Return the duration
Duration ActivityHeader::GetDuration()
{
    return duration;
}
        
///@brief Return the date
///@return The date
DateTime ActivityHeader::GetDateTime() const
{
    return date;
}

///@brief Return FC moy
unsigned int ActivityHeader::GetFCMoy()
{
    return fcMoy;
}

 ///@brief Return FC max
unsigned int ActivityHeader::GetFCMax()
{
    return fcMax;
}

///@brief Return activity type
std::string ActivityHeader::GetActivityType()
{
    return type;
}

///@brief Return location
std::string ActivityHeader::GetLocation()
{
    return location;
}

///@brief Return comments
std::string ActivityHeader::GetComments()
{
    return comments;
}

///@brief Return consumed calories
unsigned int ActivityHeader::GetCalories()
{
    return calories;
}

///@brief Return trained quality
std::string ActivityHeader::GetQuality()
{
    return quality;
}

///@brief Return the average cadence
unsigned int ActivityHeader::GetAverageCadence()
{
    return cadence;
}

///@brief Return the average power
double ActivityHeader::GetAveragePower()
{
    return power;
}
        
///@brief Return time spent in a given intensity zone
///@param zone Index of the zone of interest
double ActivityHeader::GetTimeInZone(unsigned int zone)
{
    assert(zone >= 1);
    assert(zone <= 7);
    return intensity[zone - 1];
}

///@brief Return the maximum speed
double ActivityHeader::GetMaximumSpeed()
{
    return maxSpeed;
}

///@brief Read activity header from a XML node in a Tcx File
///@param activityNode XML node to parse
void ActivityHeader::ReadFromTcxFile(xmlpp::Node* activityNode)
{
    if(activityNode->get_name() == "Activity")
    {
        const xmlpp::Element* activityNodeElement = dynamic_cast<const xmlpp::Element*> (activityNode);
        const xmlpp::Element::AttributeList &activityNodeAttributes = activityNodeElement->get_attributes();
        for(xmlpp::Element::AttributeList::const_iterator iterActivityNodeAttributes = activityNodeAttributes.begin();
            iterActivityNodeAttributes != activityNodeAttributes.end();
            ++iterActivityNodeAttributes)
        {
            const xmlpp::Attribute* activityAttribute = *iterActivityNodeAttributes;
            if(activityAttribute->get_name() == "Sport")
            {
                type = activityAttribute->get_value();
            }
        }
        // Parsing activity node children
        unsigned int lapNumber = 0;
        xmlpp::Node::NodeList activityChildren = activityNode->get_children();
        for (xmlpp::Node::NodeList::iterator iterActivityChildren = activityChildren.begin();
             iterActivityChildren != activityChildren.end();
             ++iterActivityChildren)
        {
            xmlpp::Node* activityChildNode = dynamic_cast<xmlpp::Node*>(*iterActivityChildren);
            if(activityChildNode->get_name() == "Id")
            {
                id = ((xmlpp::TextNode*) (*((activityChildNode->get_children()).begin())))->get_content();
            }
            ///@warning There can be many laps in an activity
            ///@bug This part of code is totally not compatible with new data structure
            // New code to deal with intervals definition
            if(activityChildNode->get_name() == "Lap")
            {
                DateTime startDateTime;
                Duration duration;
                double distance;
                lapNumber++;
                const xmlpp::Element* lapNodeElement = dynamic_cast<const xmlpp::Element*> (activityChildNode);
                const xmlpp::Element::AttributeList &lapNodeAttributes = lapNodeElement->get_attributes();
                for(xmlpp::Element::AttributeList::const_iterator iterLapNodeAttributes = lapNodeAttributes.begin();
                    iterLapNodeAttributes != lapNodeAttributes.end();
                    ++iterLapNodeAttributes)
                {
                    const xmlpp::Attribute* lapAttribute = *iterLapNodeAttributes;
                    if(lapAttribute->get_name() == "StartTime")
                    {
                        ///@warning Time is given in UTC zone. For France, we should add +1 hour in winter and +2 hours in summer
                        startDateTime.SetWithFormat(lapAttribute->get_value(), "yyyy-mm-ddThh:nn:ss.000Z");
                    }
                }
                // Parsing lap node children
                xmlpp::Node::NodeList lapNodeChildren = activityChildNode->get_children();
                for(xmlpp::Node::NodeList::iterator iterLapNodeChildren = lapNodeChildren.begin();
                    iterLapNodeChildren != lapNodeChildren.end();
                    ++iterLapNodeChildren)
                {
                    xmlpp::Node* lapNodeChild = dynamic_cast<xmlpp::Node*>(*iterLapNodeChildren);
                    if(lapNodeChild->get_name() == "TotalTimeSeconds")
                    {
                        double durationSeconds = StringToDouble(((xmlpp::TextNode*)(*((lapNodeChild->get_children()).begin())))->get_content());
                        duration.SetSecond((unsigned int)durationSeconds);
                        duration.Normalize();
                    }
                    if(lapNodeChild->get_name() == "DistanceMeters")
                    {
                        double distanceMeters = StringToDouble(((xmlpp::TextNode*)(*((lapNodeChild->get_children()).begin())))->get_content());
                        distance = distanceMeters / 1000.;
                    }
                    
                }   
                Interval newInterval(startDateTime, duration, UIntToString(lapNumber));
                newInterval.SetDistance(distance);
                intervals.push_back(newInterval);
            }
        }
    }
    // Update some activity header values from intervals values
    // Date of activity is the start DateTime of the first intervals
    date = intervals[0].GetStartDateTime();
    // Distance and duration are obtained from intervals
    duration.Reset();
    distance = 0.;
    for(unsigned int i = 0 ; i < intervals.size() ; ++i)
    {
        duration += intervals[i].GetDuration();
        distance += intervals[i].GetDistance();
    }
}

///@brief Read activity header form a trainingbook XML file
///@param activityNode Activity header node to read
void ActivityHeader::ReadXMLNode(xmlpp::Node* activityNode)
{
    if(activityNode->get_name() == "ActivityHeader")
    {
        xmlpp::Node::NodeList activityNodeChildren = activityNode->get_children();
        for(xmlpp::Node::NodeList::iterator iterActivityNodeChildren = activityNodeChildren.begin();
            iterActivityNodeChildren != activityNodeChildren.end();
            ++iterActivityNodeChildren)
        {
            xmlpp::Node* activityNodeChild = dynamic_cast<xmlpp::Node*>(*iterActivityNodeChildren);
            if(activityNodeChild->get_name() == "Id")
            {
                id = ((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content();
            }
            else if(activityNodeChild->get_name() == "Date")
            {
                date.SetWithFormat(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content() , "yyyy-mm-dd--hh:nn:ss");
            }
            else if(activityNodeChild->get_name() == "ActivityType")
            {
                type = ((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content();
            }
            else if(activityNodeChild->get_name() == "Distance")
            {
                distance = StringToDouble(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "Duration")
            {
                duration.SetWithFormat(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content() , "h:nn:ss");
            }
            else if(activityNodeChild->get_name() == "FCMoy")
            {
                fcMoy = StringToUInt(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "FCMax")
            {
                fcMax = StringToUInt(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "Calories")
            {
                calories = StringToUInt(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "MinutesInI1")
            {
                intensity[0] = StringToDouble(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "MinutesInI2")
            {
                intensity[1] = StringToDouble(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "MinutesInI3")
            {
                intensity[2] = StringToDouble(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "MinutesInI4")
            {
                intensity[3] = StringToDouble(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "MinutesInI5")
            {
                intensity[4] = StringToDouble(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "MinutesInI6")
            {
                intensity[5] = StringToDouble(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "MinutesInI7")
            {
                intensity[6] = StringToDouble(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "MaxSpeed")
            {
                maxSpeed = StringToDouble(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "Cadence")
            {
                cadence = StringToUInt(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "Power")
            {
                power = StringToDouble(((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content());
            }
            else if(activityNodeChild->get_name() == "Location")
            {
                location = ((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content();
            }
            else if(activityNodeChild->get_name() == "Comments")
            {
                comments = ((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content();
            }
            else if(activityNodeChild->get_name() == "Quality")
            {
                quality = ((xmlpp::TextNode*)(*((activityNodeChild->get_children()).begin())))->get_content();
            }
            /*else if(activityNodeChild->get_name() == "Lap")
            {
                LapHeader newLap;
                newLap.ReadXMLNode(activityNodeChild);
                AddLap(newLap);
            }*/
        }
    }
}

///@brief Write to XML native file
///@param fout out stream to XML file
void ActivityHeader::WriteXML(std::ofstream &fout)
{
    fout << "      <Id>" << id << "</Id>\n";
    fout << "      <Date>" << date << "</Date>\n";
    if(location.length() != 0)
        fout << "     <Location>" << location << "</Location>\n";
    if(type.length() != 0)
        fout << "      <ActivityType>" << type << "</ActivityType>\n";
    fout << "      <Distance>" << distance << "</Distance>\n";
    fout << "      <Duration>" << duration << "</Duration>\n";
    fout << "      <FCMoy>" << fcMoy << "</FCMoy>\n";
    fout << "      <FCMax>" << fcMax << "</FCMax>\n";
    if(comments.length() != 0)
        fout << "      <Comments>" << calories << "</Comments>\n";
    fout << "      <Calories>" << calories << "</Calories>\n";
    if(quality.length() != 0)
        fout << "      <Quality>" << quality << "</Quality>\n";
    fout << "      <MinutesInI1>" << GetTimeInZone(1) << "</MinutesInI1>\n";
    fout << "      <MinutesInI2>" << GetTimeInZone(2) << "</MinutesInI2>\n";
    fout << "      <MinutesInI3>" << GetTimeInZone(3) << "</MinutesInI3>\n";
    fout << "      <MinutesInI4>" << GetTimeInZone(4) << "</MinutesInI4>\n";
    fout << "      <MinutesInI5>" << GetTimeInZone(5) << "</MinutesInI5>\n";
    fout << "      <MinutesInI6>" << GetTimeInZone(6) << "</MinutesInI6>\n";
    fout << "      <MinutesInI7>" << GetTimeInZone(7) << "</MinutesInI7>\n";
    fout << "      <MaxSpeed>" << maxSpeed << "</MaxSpeed>\n";
    fout << "      <Cadence>" << cadence << "</Cadence>\n";
    fout << "      <Power>" << power << "</Power>\n";
    for(std::vector<Interval>::iterator iterInterval = intervals.begin() ; iterInterval != intervals.end() ; ++iterInterval)
    {
        fout << "      <Intervals>\n";
        (*iterInterval).WriteXML(fout);
        fout << "      </Intervals>\n"; 
    }
}

///@brief Overload of < operator to sort activities depending on their date
///@param a1 ActivityHeader left hand operand
///@param a2 ActivityHeader right hand operand
bool operator < (const ActivityHeader& a1, const ActivityHeader & a2)
{
    return (a1.GetDateTime() < a2.GetDateTime());
}
